/* Classic Ladder Project */
/* Copyright (C) 2001-2012 Marc Le Douarain */
/* http://membres.lycos.fr/mavati/classicladder/ */
/* http://www.sourceforge.net/projects/classicladder */
/* February 2011 */
/* ------------------ */
/* Hardware Interface */
/* ------------------ */
/* This library is free software; you can redistribute it and/or */
/* modify it under the terms of the GNU Lesser General Public */
/* License as published by the Free Software Foundation; either */
/* version 2.1 of the License, or (at your option) any later version. */

/* This library is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU */
/* Lesser General Public License for more details. */

/* You should have received a copy of the GNU Lesser General Public */
/* License along with this library; if not, write to the Free Software */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */


#include "classicladder.h"
#include "global.h"
#include "vars_access.h"
#include "hardware.h"

#ifdef COMEDI_SUPPORT
#ifdef __RTL__
#include <rtl.h>
#include <linux/comedilib.h>
#else
#include <comedilib.h>
#endif
#endif

#if !defined( MODULE )
#include <stdio.h>
#include <unistd.h>
#if defined(MAKE_IO_ACCESS) && !defined(__WIN32)
#include <sys/io.h>
#endif
#else
#include <asm/io.h>
#endif
#ifdef RTAI
#include "rtai.h"
#endif

#if defined(__WIN32__) && defined(MAKE_IO_ACCESS)
#include <windows.h>
#endif

#define NBR_COMEDI_DEVICES 4

#ifdef COMEDI_SUPPORT
comedi_t * ComediDev[ NBR_COMEDI_DEVICES ];
#endif


// for DLL "inpout32" to read/write I/O ports addresses under Windows (required since NT) ! */
#if defined(__WIN32__) && defined(MAKE_IO_ACCESS)
typedef short (_stdcall *inpfuncPtr)(short portaddr);
typedef void (_stdcall *oupfuncPtr)(short portaddr, short datum);
static HINSTANCE hWindowsLibInpout32;
static inpfuncPtr WindowsLibIn;
static oupfuncPtr WindowsLibOut;
void InitWindowsInpout32DLL( void )
{
	/* Load the library */
	hWindowsLibInpout32 = LoadLibrary("inpout32.dll");
	if (hWindowsLibInpout32!=NULL)
	{
		/* Get the addresses of the two read/write functions */
		WindowsLibIn = (inpfuncPtr) GetProcAddress(hWindowsLibInpout32, "Inp32");
		WindowsLibOut = (oupfuncPtr) GetProcAddress(hWindowsLibInpout32, "Out32");
		if ( WindowsLibIn==NULL || WindowsLibOut==NULL )
		{ 
			printf("GetProcAddress for functions in inpout32.dll failed !!!\n");
			hWindowsLibInpout32 = NULL;
		}
	}
	else
	{
		printf("Failed to load Windows library 'inpout32.dll' to read/write i/o ports !!!\n");
	}
}
short  Inpout32DLL_read(short int portaddr)
{
	if ( hWindowsLibInpout32 )
		return (WindowsLibIn)(portaddr);
	return 0;
}
void  Inpout32DLL_write(short int portaddr, short int datum)
{
	if ( hWindowsLibInpout32 )
		(WindowsLibOut)(portaddr,datum);
}
#endif

void InitIOConf( )
{
	int NumConf;
	int NbrConf;
	int Pass;
	StrIOConf * pConf;
	for( Pass=0; Pass<2; Pass++)
	{
		NbrConf = (Pass==0)?NBR_INPUTS_CONF:NBR_OUTPUTS_CONF;
		for( NumConf=0; NumConf<NbrConf; NumConf++ )
		{
			pConf = (Pass==0)?&InfosGene->InputsConf[ NumConf ]:&InfosGene->OutputsConf[ NumConf ];
			pConf->FirstClassicLadderIO = -1;
			pConf->DeviceType = DEVICE_TYPE_NONE;
			pConf->SubDevOrAdr = 0;
			pConf->FirstChannel = 0;
			pConf->NbrConsecutivesChannels = 1;
			pConf->FlagInverted = 0;
			pConf->ConfigData = 0;
		}
	}
}

//called in "scan inputs" thread
void ReadPhysicalInputs( void )
{
	int NumConf;
	StrIOConf * pConf;
	unsigned int DatasRead;
	unsigned int Mask;
	int VarNumber;
	for( NumConf=0; NumConf<NBR_INPUTS_CONF; NumConf++ )
	{
		pConf = &InfosGene->InputsConf[ NumConf ];
		/* something mapped ? */
		if ( pConf->FirstClassicLadderIO!=-1 )
		{
			DatasRead = 0;
			
			/* read the physical inputs */
#ifdef MAKE_IO_ACCESS
			if ( pConf->DeviceType==DEVICE_TYPE_DIRECT_ACCESS )
			{
#ifdef __WIN32__
				DatasRead = Inpout32DLL_read( pConf->SubDevOrAdr );
#else
				DatasRead = inb( pConf->SubDevOrAdr );
#endif
			}
#endif

#ifdef COMEDI_SUPPORT
			if ( pConf->DeviceType>=DEVICE_TYPE_COMEDI )
			{
				int ComediNum = pConf->DeviceType-DEVICE_TYPE_COMEDI;
				/* device is opened ? */
				if ( ComediDev[ ComediNum ] )
				{
					/* try to use the optimized way if possible */
					if ( pConf->FirstChannel<=31 && pConf->NbrConsecutivesChannels+pConf->FirstChannel<=32 )
					{
						comedi_dio_bitfield( ComediDev[ ComediNum ],
									pConf->SubDevOrAdr, 0, &DatasRead );
					}
					else
					{
						int ScanEach;
						int BitValue;
						Mask = 1<<pConf->FirstChannel;
						for( ScanEach=pConf->FirstChannel; ScanEach<pConf->FirstChannel+pConf->NbrConsecutivesChannels; ScanEach++ )
						{
							comedi_dio_read( ComediDev[ ComediNum ],
									pConf->SubDevOrAdr, ScanEach, &BitValue );
							if ( BitValue )
								DatasRead = DatasRead|Mask;
							Mask = Mask<<1;
						}
					}
				}
			}
#endif

			if ( pConf->FlagInverted )
				DatasRead = ~DatasRead;

			/* Copy the datas read on the inputs variables */
			Mask = 1<<pConf->FirstChannel;
			for (VarNumber=pConf->FirstClassicLadderIO; VarNumber<pConf->FirstClassicLadderIO+pConf->NbrConsecutivesChannels; VarNumber++)
			{
				StrInputFilterAndState * pInput = &InputFilterAndStateArray[ VarNumber ];
				// shift raw buffer
				pInput->BuffRawInput = pInput->BuffRawInput<<1;
				// last state (is on ? if yes, set 1 to last bit buffer)
				if ( DatasRead & Mask )
					pInput->BuffRawInput = pInput->BuffRawInput | 1;
				// filter, is stable "1" ?
				if ( ( pInput->BuffRawInput & pInput->FilterMask ) == pInput->FilterMask )
					pInput->InputStateForLogic =  1;
				// filter, is stable "0" ?
				if ( ( pInput->BuffRawInput & pInput->FilterMask ) == 0 )
					pInput->InputStateForLogic =  0;
					
//////				WriteVar(VAR_PHYS_INPUT,VarNumber,(DatasRead & Mask)?1:0);
				Mask = Mask<<1;
			}
		}
	}


}

// called in "logic" thread to refresh inputs var with filtered state
void CopyInputsStatesToInputsVars( void )
{
	int NumInp;
	for( NumInp=0; NumInp<NBR_PHYS_INPUTS; NumInp++ )
	{
		StrInputFilterAndState * pInput = &InputFilterAndStateArray[ NumInp ];
		// real filtered value? interesting to avoid battle with simul checkboxes clicked by hand!
		if ( pInput->InputStateForLogic!=-1 )
		{
//printf("Write var phys input %d\n",NumInp);
			WriteVar( VAR_PHYS_INPUT,NumInp, pInput->InputStateForLogic );
		}
	}
}

void WritePhysicalOutputs( char OnlyForLifeLed )
{
	if ( OnlyForLifeLed )
	{
		/* usefull, if life led shared on a i/o port, and logic "stopped", so outputs not refreshed ! */
#if defined( HARD_LIFE_LED_PORT ) && defined (HARD_LIFE_LED_BIT )
#ifdef __WIN32__
		Inpout32DLL_write( HARD_LIFE_LED_PORT, LifeLedDatasToWrite ); 
#else
		outb( LifeLedDatasToWrite, HARD_LIFE_LED_PORT );
#endif
#endif
	}
	else
	{
		int NumConf;
		StrIOConf * pConf;
		unsigned int DatasToWrite;
		unsigned int Mask;
		int VarNumber;
		for( NumConf=0; NumConf<NBR_OUTPUTS_CONF; NumConf++ )
		{
			pConf = &InfosGene->OutputsConf[ NumConf ];
			/* something mapped ? */
			if ( pConf->FirstClassicLadderIO!=-1 )
			{
				DatasToWrite = 0;
				/* get the datas to write from outputs variables */
				Mask = 1<<pConf->FirstChannel;
				for (VarNumber=pConf->FirstClassicLadderIO; VarNumber<pConf->FirstClassicLadderIO+pConf->NbrConsecutivesChannels; VarNumber++)
				{
					if ( ReadVar(VAR_PHYS_OUTPUT,VarNumber) )
						DatasToWrite = DatasToWrite | Mask;
					Mask = Mask<<1;
				}
				if ( pConf->FlagInverted )
					DatasToWrite = ~DatasToWrite;
				
				/* write the physical outputs */
#ifdef MAKE_IO_ACCESS
				if ( pConf->DeviceType==DEVICE_TYPE_DIRECT_ACCESS )
				{
					/* verify if not same register port shared with life led bit ? (if yes, do "or") */
#if defined( HARD_LIFE_LED_PORT ) && defined (HARD_LIFE_LED_BIT )
					if ( pConf->SubDevOrAdr==HARD_LIFE_LED_PORT )
					{
						// mask off life led bit (usefull if flag "inverted" before... so bit already at "1"!)
						DatasToWrite = DatasToWrite & (~(1<<HARD_LIFE_LED_BIT ));
						DatasToWrite = DatasToWrite | LifeLedDatasToWrite;
					}
#endif
#ifdef __WIN32__
					Inpout32DLL_write( pConf->SubDevOrAdr, DatasToWrite ); 
#else
					outb( DatasToWrite, pConf->SubDevOrAdr );
#endif
				}
#endif

#ifdef COMEDI_SUPPORT
				if ( pConf->DeviceType>=DEVICE_TYPE_COMEDI )
				{
					int ComediNum = pConf->DeviceType-DEVICE_TYPE_COMEDI;
					/* device is opened ? */
					if ( ComediDev[ ComediNum ] )
					{
						/* try to use the optimized way if possible */
						if ( pConf->FirstChannel<=31 && pConf->NbrConsecutivesChannels+pConf->FirstChannel<=32 )
						{
							int WriteMask = (1<<pConf->NbrConsecutivesChannels) - 1;
							WriteMask = WriteMask<<pConf->FirstChannel;
							comedi_dio_bitfield( ComediDev[ ComediNum ],
										pConf->SubDevOrAdr, WriteMask, &DatasToWrite );
						}
						else
						{
							int ScanEach;
							Mask = 1<<pConf->FirstChannel;
							for( ScanEach=pConf->FirstChannel; ScanEach<pConf->FirstChannel+pConf->NbrConsecutivesChannels; ScanEach++ )
							{
								int BitToWrite = DatasToWrite&Mask;
								comedi_dio_write( ComediDev[ ComediNum ],
									pConf->SubDevOrAdr, ScanEach, BitToWrite?1:0 );
								Mask = Mask<<1;
							}
						}
					}
				}
#endif
			}
		}
	}
}

void InitHardware( void )
{
#if defined(__WIN32__) && defined(MAKE_IO_ACCESS)
	InitWindowsInpout32DLL( );
#endif
}
void EndHardware( void )
{
#if defined(__WIN32__) && defined(MAKE_IO_ACCESS)
	if ( hWindowsLibInpout32 )
		FreeLibrary(hWindowsLibInpout32);
#endif
}

/* for Comedi under RTLinux, is called from init_module( ) and the mask parameter given tell
   which comedi devices to open.
   Under Linux the mask parameter is not used and is taken from the I/O file previously loaded */
void OpenHardware( int ComediToOpenMask )
{
#ifdef COMEDI_SUPPORT
	int ComediNum;
	int Mask;
#ifndef __RTL__
	int Pass;
	HardwareErrMsgToDisplay[ 0 ] = '\0';
	/* determine the comedi devices to open from the I/O Mapping Config */
	ComediToOpenMask = 0;
	for( Pass=0; Pass<2; Pass++)
	{
		StrIOConf * pConf;
		int NumConf;
		int NbrConf = (Pass==0)?NBR_INPUTS_CONF:NBR_OUTPUTS_CONF;
		for( NumConf=0; NumConf<NbrConf; NumConf++ )
		{
			pConf = (Pass==0)?&InfosGene->InputsConf[ NumConf ]:&InfosGene->OutputsConf[ NumConf ];
			if ( pConf->FirstClassicLadderIO!=-1 )
			{
				if ( pConf->DeviceType>=DEVICE_TYPE_COMEDI )
					ComediToOpenMask = ComediToOpenMask | (1<<(pConf->DeviceType-DEVICE_TYPE_COMEDI));
			}
		}
	}
#endif

	debug_printf("Open the Comedi hardware...\n");
	/* Open each comedi devices */
	Mask = 0x01;
	for( ComediNum=0; ComediNum<NBR_COMEDI_DEVICES; ComediNum++ )
	{
		ComediDev[ ComediNum ] = 0; /* not opened */
		if ( ComediToOpenMask & Mask )
		{
			char Buffer[ 20 ];
			sprintf( Buffer, "/dev/comedi%d", ComediNum );
			debug_printf( "Opening %s...", Buffer );
			ComediDev[ ComediNum ] = comedi_open( Buffer );
			if ( !ComediDev[ ComediNum ] )
			{
				debug_printf("failed!!!\n");
				sprintf( InfosGene->HardwareErrMsgToDisplay, "Failed to open /dev/comedi%d", ComediNum );
			}
			else
			{
				debug_printf("Ok!\n");
			}
		}
		Mask = Mask<<1;
	}
#endif
}

/* Under Linux, call ioperm( ) for the necessary adresses ports to access. */
/* For Comedi, configure the direction of bidirectional lines. */
void ConfigHardware( char ForOutputs )
{
	int NumConf;
	int NbrConf;
	int Pass = ForOutputs;
	StrIOConf * pConf;
	// stop the logic refresh before configuring the following...
	StopRunIfRunning( );
#ifdef MODULE
	debug_printf("Configure the hardware (%s)...\n",ForOutputs?"outputs":"inputs");
#else
	printf("Configure the hardware (%s)...\n",ForOutputs?"outputs":"inputs");
#endif
//////	for( Pass=0; Pass<2; Pass++)
	{
		NbrConf = (Pass==0)?NBR_INPUTS_CONF:NBR_OUTPUTS_CONF;
		for( NumConf=0; NumConf<NbrConf; NumConf++ )
		{
			pConf = (Pass==0)?&InfosGene->InputsConf[ NumConf ]:&InfosGene->OutputsConf[ NumConf ];
//////			if ( pConf->FirstClassicLadderIO!=-1 )
			{
#ifdef MAKE_IO_ACCESS
				if ( pConf->DeviceType==DEVICE_TYPE_DIRECT_ACCESS || pConf->DeviceType==DEVICE_TYPE_DIRECT_CONFIG )
				{
					char cPortAccessError = FALSE;
#if !defined( MODULE ) && !defined( __WIN32__ )
					printf("Config hardware, ask ioperm 0x%X\n",pConf->SubDevOrAdr);
					if ( ioperm( pConf->SubDevOrAdr, 1, 1 ) )
					{
						printf("!!!Failed in ioperm call for the I/O port : 0x%X (you must be root)\n", pConf->SubDevOrAdr );
						pConf->FirstClassicLadderIO = -1;
						pConf->DeviceType = DEVICE_TYPE_NONE;
						printf("!!!This direct access port has been disabled in the I/O conf to avoid to crash later...\n" );
						sprintf( InfosGene->HardwareErrMsgToDisplay, "Failed in ioperm call for the I/O port : 0x%X (you must be root)", pConf->SubDevOrAdr );
						cPortAccessError = TRUE;
					}
#endif
					if ( pConf->DeviceType==DEVICE_TYPE_DIRECT_CONFIG && !cPortAccessError )
					{
						int DatasToWrite = pConf->ConfigData;
					/* verify if not same register direction i/o port shared with life led bit ? (if yes, do "or") */
#if defined( HARD_LIFE_LED_DIR_PORT )
						if ( pConf->SubDevOrAdr==HARD_LIFE_LED_DIR_PORT )
							DatasToWrite = DatasToWrite | HARD_LIFE_LED_DIR_VALUE_TO_OR;
#endif
						printf("Config hardware, write port 0x%X with config data %d\n",pConf->SubDevOrAdr, DatasToWrite);
#ifdef __WIN32__
						Inpout32DLL_write( pConf->SubDevOrAdr, DatasToWrite ); 
#else
						outb( DatasToWrite, pConf->SubDevOrAdr );
#endif
					}
				}
#endif

#ifdef COMEDI_SUPPORT
				if ( pConf->DeviceType>=DEVICE_TYPE_COMEDI )
				{
					int ScanEach;
					int ComediNum = pConf->DeviceType-DEVICE_TYPE_COMEDI;
					/* device is opened ? */
					if ( ComediDev[ ComediNum ] )
					{
						for( ScanEach=pConf->FirstChannel; ScanEach<pConf->FirstChannel+pConf->NbrConsecutivesChannels; ScanEach++ )
						{
							comedi_dio_config( ComediDev[ ComediNum ],
									pConf->SubDevOrAdr, ScanEach, (Pass==0)?COMEDI_INPUT:COMEDI_OUTPUT );
						}
					}
				}
#endif
			}
		}
	}
	// start the logic now !
	RunBackIfStopped( );
}
void ConfigHardwareForLifeLed( )
{
#if defined( HARD_LIFE_LED_PORT ) && defined (HARD_LIFE_LED_BIT )
#if !defined( MODULE ) && !defined( __WIN32__ )
	printf( "Config hardware for life led, ask ioperm 0x%X\n", HARD_LIFE_LED_PORT );
	if ( ioperm( HARD_LIFE_LED_PORT, 1, 1 ) )
	{
		printf("!!!Failed in ioperm call for the I/O port : 0x%X (you must be root)\n", HARD_LIFE_LED_PORT );
	}
#if defined( HARD_LIFE_LED_DIR_PORT )
	printf( "Config hardware for life led (i/o dir), ask ioperm 0x%X + set value\n", HARD_LIFE_LED_DIR_PORT );
	if ( ioperm( HARD_LIFE_LED_DIR_PORT, 1, 1 ) )
	{
		printf("!!!Failed in ioperm call for the I/O port : 0x%X (you must be root)\n", HARD_LIFE_LED_DIR_PORT );
	}
	else
	{
		outb( HARD_LIFE_LED_DIR_VALUE_TO_OR, HARD_LIFE_LED_DIR_PORT );
	}
#endif
#endif
#endif
}

